package hu.elte.inf.holiuml.fileutil;

import hu.elte.inf.holiuml.model.Document;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileFilter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

/**
 * File utilities: open and save a Document object from and to
 * XML files. This class has package visibility, as its intended
 * use is only through the PersistenceService interface.
 * 
 * @author Jozsef Sipos
 */
class XMLPersister implements PersistenceService {

	private static final String OPEN_DIALOG_TITLE = "Open HoliUML Document file";
	private static final String SAVE_DIALOG_TITLE = "Save HoliUML Document file";
	private static final String OVERWRITE_EXISTING_WITH_NAMEPARAM =
		"File %s already exists, overwrite?";
	
	// Singleton instance
	private static XMLPersister instance;
	
	// FileFilter for open and save HoliUMLDocument files
	private static FileFilter holiUMLFileFilter = new FileFilter() {

		@Override
		public boolean accept(File file) {
			return file.getName().endsWith(".sum") || file.isDirectory();
		}

		@Override
		public String getDescription() {
			return "Simple HoliUML Document files (*.sum)";
		}

	};

	/**
	 * Use this method to access an XML persister object.
	 * The returned object can be used to open and save
	 * UML Documents in XML format.
	 * 
	 * @return Default File XMLPersister
	 * @throws JAXBException If cannot create JAXB context
	 */
	public static PersistenceService getInstance() throws JAXBException {
		if (instance == null) {
			instance = new XMLPersister();
		}
		return instance;
	}

	// Cannot instantiate this class from outside of this package
	private XMLPersister() throws JAXBException {
		documentFileMap = new HashMap<Document, File>();
		JAXBContext jaxbContext = JAXBContext.newInstance(HoliUMLDocument.class);
		marshaller = jaxbContext.createMarshaller();
		unmarshaller = jaxbContext.createUnmarshaller();
	}

	/* ************** Instance variables and methods ************* */
	
	/* Opened files, to know where to save them
	 * This map will only be growing, entries are never removed from it.
	 * However it is assumed that there will not be that much open/save
	 * actions during a single run of the application, that it would cause
	 * OutOfMemory error.
	 */
	private Map<Document, File> documentFileMap;
	
	// Binder context
	private Marshaller marshaller;
	private Unmarshaller unmarshaller;

	
	/**
	 * This method is called when the user wants to open a
	 * previously saved Simple HoliUML Document File.
	 * It will open a JFileChooser dialog, in which
	 * the user can select the file to be loaded.
	 * 
	 * @return The Document object created from the XML
	 * 	       content of the selected file, or null,
	 *         if the user has changed his or her mind and
	 *         pressed Cancel.
	 * 
	 * @throws IOException Thrown in case of the followings:
	 *                     Cannot read file, wrong file format,
	 *                     insufficient privileges, other IO error.
	 */
	public Document open(JFrame mainFrame) throws IOException {

		JFileChooser fileChooser = new JFileChooser();
		fileChooser.setDialogTitle(OPEN_DIALOG_TITLE);
		fileChooser.setDialogType(JFileChooser.OPEN_DIALOG);
		fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
		fileChooser.setMultiSelectionEnabled(false);
		fileChooser.setFileFilter(holiUMLFileFilter);

		if (JFileChooser.APPROVE_OPTION == fileChooser.showOpenDialog(mainFrame)) {

			File file = fileChooser.getSelectedFile();
			Document document = getModelFrom(file);

			// Overwrite, if existed
			documentFileMap.put(document, file);

			return document;
		} else {
			// Cancel pressed
			return null;
		}
	}

	/**
	 * Save the passed document to file.
	 * If it was previously saved, the it is saved to the same
	 * file. If not, the user is going to be asked for the file
	 * name.
	 * 
	 * @param document The model to be saved
	 * @throws IOException Any IO error during saving
	 */
	public void save(Document document, JFrame mainFrame) throws IOException {

		if (documentFileMap.containsKey(document)) {
			File file = documentFileMap.get(document);
			saveModel(document, file);
		} else {
			saveAs(document, mainFrame);
		}
	}

	/**
	 * Wraps the document in a HoliUMLDocument, and saves it in the given file.
	 * 
	 * @param document       
	 * @param file            
	 * @throws IOException
	 */
	private void saveModel(Document document, File file) throws IOException {
		try {
			HoliUMLDocument docWrapper = new HoliUMLDocument(document);
			docWrapper.generateReferenceInfo();
			marshaller.marshal(docWrapper, file);
		} catch (JAXBException jaxbEx) {
			throw new IOException(jaxbEx);
		}
	}

	/**
	 * @see PersistenceService#saveAs(Document) 
	 */
	public void saveAs(Document document, JFrame mainFrame) throws IOException {
		
		JFileChooser fileChooser = new JFileChooser();
		fileChooser.setDialogTitle(SAVE_DIALOG_TITLE);
		fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
		fileChooser.setFileFilter(holiUMLFileFilter);

		if (JFileChooser.APPROVE_OPTION == fileChooser.showSaveDialog(mainFrame)) {

			File file = fileChooser.getSelectedFile();

			if( !file.getName().endsWith(".sum")) {
				file = new File(file.getPath() + ".sum");
			}
			
			if(file.exists()) {
				int confirm = JOptionPane.showConfirmDialog(mainFrame,
						String.format(OVERWRITE_EXISTING_WITH_NAMEPARAM,
								file.getName()));
				if(confirm != JOptionPane.YES_OPTION) {
					return;
				}
			}
			
			documentFileMap.put(document, file);
			
			saveModel(document, file);

		}
	}

	/**
	 * Builds up a complete Document object from the information stored in the
	 * given File.
	 * 
	 * @param file           XML Source
	 * @return Document
	 * @throws IOException
	 */
	private Document getModelFrom(File file) throws IOException {
		try {
			HoliUMLDocument docWrapper =
				(HoliUMLDocument)unmarshaller.unmarshal(file);
			docWrapper.restoreReferences();
			return docWrapper.getDocument();
			
		} catch (JAXBException jaxbEx) {
			throw new IOException(jaxbEx);
		}
	}
}
